package com.cloudlead.common.persistence.bean;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.lang3.reflect.FieldUtils;

import com.cloudlead.common.lang.annotation.IgnoreProperty;
import com.cloudlead.common.lang.annotation.Property;
import com.cloudlead.common.lang.persistence.IdEntity;
import com.cloudlead.common.persistence.impl.SimpleEntityPathResolver;
import com.mysema.query.Tuple;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;

/**
 * 领域和VO对象之间的转换
 *
 * @author dean.lu
 */
public class BeanConvertUtils {
    private static final String ID = "id";
    /**
     * 缓存bean定义
     */
    private static Map<Class<?>, BeanDesc> cachedBeanParser = new ConcurrentHashMap<>();

    static {
        Converter byteArrayConverter = new Converter() {

            @SuppressWarnings("rawtypes")
            @Override
            public Object convert(Class type, Object value) {
                if (null == value)
                    return null;
                if (value instanceof String) {
                    value = ((String) value).getBytes();
                }
                return value;
            }
        };
        ConvertUtils.register(byteArrayConverter, byte[].class);

        Converter stringConverter = new Converter() {

            @SuppressWarnings("rawtypes")
            @Override
            public Object convert(Class type, Object value) {
                if (null == value)
                    return null;
                if (value instanceof byte[]) {
                    value = new String((byte[]) value);
                }
                return value;
            }
        };
        ConvertUtils.register(stringConverter, String.class);
    }

    /**
     * 注册VO类型
     *
     * @param voClass
     */
    public static void registerVOType(Class<?> voClass) {
        if (null == cachedBeanParser.get(voClass)) {
            cachedBeanParser.put(voClass, new BeanDesc(voClass));
        }
    }

    /**
     * 获取VO定义
     *
     * @param voClass
     * @return
     */
    public static BeanDesc getVOBeanDesc(Class<?> voClass) {
        BeanDesc beanDesc = cachedBeanParser.get(voClass);
        if (null == beanDesc) {
            beanDesc = new BeanDesc(voClass);
            cachedBeanParser.put(voClass, beanDesc);
        }
        return beanDesc;
    }

    /**
     * 转换VO到领域对象
     *
     * @param vos
     * @param domainClass
     * @return
     */
    public static <T> List<T> convertVOToDomain(Collection<?> vos,
                                                Class<T> domainClass) {
        List<T> answer = new ArrayList<>();
        for (Object domain : vos) {
            T vo = convertVOToDomain(domain, domainClass);
            if (null != vo)
                answer.add(vo);
        }
        return answer;
    }

    /**
     * 转换VO到领域对象
     *
     * @param vo
     * @param domainClass
     * @return
     */
    public static <T> T convertVOToDomain(Object vo, Class<T> domainClass) {
        try {
            if (null == vo) {
                return null;
            }

            T domainInstance = domainClass.newInstance();
            Class<?> voClass = vo.getClass();
            BeanDesc beanParser = cachedBeanParser.get(voClass);
            if (null == beanParser) {
                beanParser = new BeanDesc(voClass);
                cachedBeanParser.put(voClass, beanParser);
            }
            for (PropertyDesc propertyDescriptor : beanParser.getProperties()) {
                String voPropertyName = propertyDescriptor.getName();
                // 只转换基本数据类型或者枚举的属性
                Class<?> propertyType = propertyDescriptor.getType();
                if (isPrimitive(propertyType) || propertyType.isEnum()) {

                    // 是否有需要忽略的字段
                    IgnoreProperty ignoreField = propertyDescriptor
                            .getIgnoreProperty();
                    if (null == ignoreField) {
                        String domainPropertyName = voPropertyName;
                        // 是否对字段重命名
                        Property proField = propertyDescriptor.getProperty();
                        if (null != proField) {
                            String name = proField.name();
                            if (null != name && name.length() > 0) {
                                domainPropertyName = name;
                            }

                        }

                        // 忽略领域类中没有的属性
                        Field declaredField = FieldUtils.getField(domainClass,
                                domainPropertyName, true);
                        if (null != declaredField) {
                            Class<?> domainFieldType = declaredField.getType();
                            Object propertyValue = propertyDescriptor
                                    .getValue(vo);
                            if (null != propertyValue) {
                                propertyValue = transationDataType(
                                        propertyType, domainFieldType,
                                        propertyValue);
                                FieldUtils
                                        .writeField(domainInstance,
                                                domainPropertyName,
                                                propertyValue, true);
                            }
                        }
                    }
                }
            }
            // 处理属于关联关系
            if (beanParser.hasBelongsTo()) {
                List<AssociatedDesc> belongsTos = beanParser.getBelongsTos();
                for (AssociatedDesc belongsTo : belongsTos) {
                    if (null != belongsTo.getRootProperty()
                            && belongsTo.getRootProperty().length() > 0) {
                        String property = belongsTo.getProperty();
                        Object propValue = FieldUtils.readField(vo, property,
                                true);
                        if (null != propValue) {
                            Object propIdValue = FieldUtils.readField(
                                    propValue, ID, true);
                            FieldUtils.writeField(domainInstance,
                                    belongsTo.getRootProperty(), propIdValue,
                                    true);
                        }
                    }
                }
            }
            return domainInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            throw new BeanConvertException("Convert vo to domain error.", e);
        }

    }

    private static Object transationDataType(Class<?> sourceType,
                                             Class<?> destType, Object propertyValue) {
        // 转换枚举值
        if (sourceType.isEnum()) {
            Object[] enumConstants = sourceType.getEnumConstants();
            for (int i = 0; i < enumConstants.length; i++) {
                if (propertyValue.equals(enumConstants[i])) {
                    if (destType.equals(Integer.class)) {
                        propertyValue = i;
                    } else {
                        propertyValue = ((Enum<?>) enumConstants[i]).name();
                    }
                    break;
                }
            }
        }
        // 转换成枚举值
        if (destType.isEnum()) {
            Object[] enumConstants = destType.getEnumConstants();
            for (int i = 0; i < enumConstants.length; i++) {
                if (sourceType.equals(Integer.class)) {
                    if (propertyValue.equals(i)) {
                        propertyValue = enumConstants[i];
                        break;
                    }
                } else {
                    if (propertyValue.equals(((Enum<?>) enumConstants[i]).name())) {
                        propertyValue = enumConstants[i];
                        break;
                    }
                }

            }
        }
        propertyValue = ConvertUtils.convert(propertyValue, destType);
        return propertyValue;
    }

    /**
     * 判断是否为基本数据类型
     *
     * @param type
     * @return
     */
    public static boolean isPrimitive(Class<?> type) {
        return type.isPrimitive() || Long.class.equals(type)
                || Integer.class.equals(type) || Short.class.equals(type)
                || Float.class.equals(type) || Double.class.equals(type)
                || BigDecimal.class.equals(type) || Date.class.equals(type)
                || Timestamp.class.equals(type) || LocalDate.class.equals(type)
                || DateTime.class.equals(type) || Boolean.class.equals(type)
                || String.class.equals(type);
    }

    /**
     * 转换领域对象到VO
     *
     * @param domain
     * @param voClass
     * @return
     */
    public static <T> T convertDomainToVO(Object domain, Class<T> voClass) {
        try {
            if (null == domain) {
                return null;
            }

            if (domain instanceof Tuple) {
                return convertTupleToVO((Tuple) domain, voClass);
            }

            if (domain instanceof IdEntity) {
                if (null == ((IdEntity<?>) domain).getId()) {
                    return null;
                }
            }

            T voInstance = voClass.newInstance();
            BeanDesc beanParser = cachedBeanParser.get(voClass);
            if (null == beanParser) {
                beanParser = new BeanDesc(voClass);
                cachedBeanParser.put(voClass, beanParser);
            }
            for (PropertyDesc propertyDescriptor : beanParser.getProperties()) {
                String voPropertyName = propertyDescriptor.getName();
                // 只转换基本数据类型的属性
                Class<?> propertyType = propertyDescriptor.getType();
                if (isPrimitive(propertyType) || propertyType.isEnum()) {
                    // 是否有需要忽略的字段
                    IgnoreProperty ignoreField = propertyDescriptor
                            .getIgnoreProperty();
                    if (null == ignoreField) {
                        String domainPropertyName = voPropertyName;
                        // 是否对字段重命名
                        Property proField = propertyDescriptor.getProperty();
                        if (null != proField) {
                            String name = proField.name();
                            if (null != name && name.length() > 0) {
                                domainPropertyName = name;
                            }

                        }
                        // 忽略领域类中没有的属性
                        Field declaredField = FieldUtils.getField(
                                domain.getClass(), domainPropertyName, true);
                        if (null != declaredField) {
                            Class<?> domainFieldType = declaredField.getType();
                            Object fieldValue = FieldUtils.readField(domain,
                                    domainPropertyName, true);
                            if (null != fieldValue) {

                                fieldValue = transationDataType(
                                        domainFieldType, propertyType,
                                        fieldValue);
                                propertyDescriptor.setValue(voInstance,
                                        fieldValue);
                            }
                        }
                    }
                }
            }

            // 处理属于关联关系
            if (beanParser.hasBelongsTo()) {
                List<AssociatedDesc> belongsTos = beanParser.getBelongsTos();
                for (AssociatedDesc belongsTo : belongsTos) {
                    if (null != belongsTo.getRootProperty()
                            && belongsTo.getRootProperty().length() > 0) {
                        Object assfieldValue = FieldUtils.readField(domain,
                                belongsTo.getRootProperty(), true);
                        if (null != assfieldValue && !"".equals(assfieldValue)) {
                            String property = belongsTo.getProperty();
                            PropertyDesc propertyDesc = beanParser
                                    .getProperty(property);
                            Class<?> propType = propertyDesc.getType();
                            Object propInstance = propType.newInstance();
                            FieldUtils.writeField(propInstance, ID,
                                    assfieldValue, true);
                            propertyDesc.setValue(voInstance, propInstance);
                        }
                    }
                }
            }
            return voInstance;
        } catch (InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            throw new BeanConvertException("Convert domain to vo error.", e);
        }

    }

    /**
     * 转换领域对象到VO
     *
     * @param domains
     * @param voClass
     * @return
     */
    public static <T> List<T> convertDomainToVO(Collection domains,
                                                Class<T> voClass) {
        List<T> answer = new ArrayList<>();
        if (null == domains || domains.isEmpty()) {
            return answer;
        }
        Object next = domains.iterator().next();
        if (next instanceof Tuple) {
            return convertTupleToVO(domains, voClass);
        }
        for (Object domain : domains) {
            if (null != domain) {
                T vo = convertDomainToVO(domain, voClass);
                if (null != vo)
                    answer.add(vo);
            }
        }
        return answer;
    }

    /**
     * 转换结果集到VO对象
     *
     * @param result
     * @param voClass
     * @return
     */
    public static <T> T convertTupleToVO(Tuple result, Class<T> voClass) {
        try {
            T voObj = convertSingeleObj(result, voClass);
            if (null == voObj) {
                return null;
            }
            convertNestedTupleToVO(result, voClass, voObj, null);
            return voObj;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            throw new BeanConvertException("Convert tuple to vo error.", e);
        }
    }

    /**
     * 转换结果集到VO对象
     *
     * @param results
     * @param voClass
     * @return
     */
    public static <T> List<T> convertTupleToVO(Collection<Tuple> results,
                                               Class<T> voClass) {
        List<T> answer = new ArrayList<>();
        if (null != results) {
            for (Tuple tuple : results) {
                T voObj;
                try {
                    voObj = convertSingeleObj(tuple, voClass);
                    if (null == voObj) {
                        continue;
                    }
                    // 去除重复的对象
                    if (answer.contains(voObj)) {
                        Iterator<T> iterator = answer.iterator();
                        while (iterator.hasNext()) {
                            T t = iterator.next();
                            if (voObj.equals(t)) {
                                voObj = t;
                                break;
                            }
                        }
                    }
                    convertNestedTupleToVO(tuple, voClass, voObj, null);
                    if (!answer.contains(voObj)) {
                        answer.add(voObj);
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                    throw new BeanConvertException(
                            "Convert tuple to vo error.", e);
                }
            }
        }
        return answer;
    }

    private static <T> void convertNestedTupleToVO(Tuple result,
                                                   Class<T> voClass, Object voObj, Class<?> parentClass) {
        try {
            if (null == voObj) {
                return;
            }
            BeanDesc beanParser = cachedBeanParser.get(voClass);
            if (null == beanParser) {
                beanParser = new BeanDesc(voClass);
                cachedBeanParser.put(voClass, beanParser);
            }
            // 处理属于关联关系
            if (beanParser.hasBelongsTo()) {
                List<AssociatedDesc> belongsTos = beanParser.getBelongsTos();
                for (AssociatedDesc belongsTo : belongsTos) {
                    parserBelongsTo(belongsTo,
                            beanParser.getProperty(belongsTo.getProperty()),
                            result, voClass, voObj);
                }
            }

            // 处理一对多关联关系
            if (beanParser.hasHasMany()) {
                List<AssociatedDesc> hasManys = beanParser.getHasManys();
                for (AssociatedDesc hasMany : hasManys) {
                    parserHasMany(hasMany,
                            beanParser.getProperty(hasMany.getProperty()),
                            result, voClass, voObj, parentClass);
                }
            }

        } catch (ClassNotFoundException | IllegalArgumentException
                | IllegalAccessException e) {
            e.printStackTrace();
            throw new BeanConvertException("Convert tuple to vo error.", e);
        }
    }

    private static <T> void parserHasMany(AssociatedDesc hasMany,
                                          PropertyDesc propertyParser, Tuple result, Class<T> voClass,
                                          Object voObj, Class<?> parentClass) throws ClassNotFoundException,
            IllegalAccessException {
        Class<?> associatedType = hasMany.getAssociatedClass();

        // 去除循环引用，支持双向关联
        if (associatedType.equals(parentClass)) {
            return;
        }
        Object propertyValue = propertyParser.getValue(voObj);
        // 集合属性初始化，只支持Set和List
        if (null == propertyValue) {
            Class<?> propertyType = propertyParser.getType();
            if (propertyType.equals(Set.class)) {
                propertyValue = new LinkedHashSet<>();
            } else if (propertyType.equals(List.class)) {
                propertyValue = new ArrayList<>();
            } else {
                throw new IllegalArgumentException(
                        "Has many property only supported set or list collection type.");
            }
        }
        @SuppressWarnings("unchecked")
        Collection<Object> collection = (Collection<Object>) propertyValue;
        Object associatedObj = convertSingeleObj(result, associatedType);
        // 去除重复的对象
        if (null != associatedObj) {
            if (collection.contains(associatedObj)) {
                Iterator<?> iterator = collection.iterator();
                while (iterator.hasNext()) {
                    Object t = iterator.next();
                    if (associatedObj.equals(t)) {
                        associatedObj = t;
                        break;
                    }
                }
            } else {
                collection.add(associatedObj);
            }
        }
        propertyParser.setValue(voObj, collection);
        convertNestedTupleToVO(result, associatedType, associatedObj, voClass);
    }

    private static <T> void parserBelongsTo(AssociatedDesc belongsTo,
                                            PropertyDesc propertyParser, Tuple result, Class<T> voClass,
                                            Object voObj) throws ClassNotFoundException, IllegalAccessException {
        Class<?> associatedType = belongsTo.getAssociatedClass();
        Object associatedObj = propertyParser.getValue(voObj);
        if (null != associatedObj) {
            Object associatedObj_ = convertSingeleObj(result, associatedType);
            if (null != associatedObj_) {
                BeanDesc beanParser = cachedBeanParser.get(associatedType);
                if (null == beanParser) {
                    beanParser = new BeanDesc(associatedType);
                    cachedBeanParser.put(associatedType, beanParser);
                }
                for (PropertyDesc propertyDesc : beanParser.getProperties()) {
                    Object propertyValue = propertyDesc.getValue(associatedObj);
                    if (null != propertyValue) {
                        propertyDesc.setValue(associatedObj_, propertyValue);
                    }
                }
                associatedObj = associatedObj_;
            }

        }
        propertyParser.setValue(voObj, associatedObj);
        convertNestedTupleToVO(result, associatedType, associatedObj, voClass);
    }

    private static <T> T convertSingeleObj(Tuple result, Class<T> voClass)
            throws ClassNotFoundException {
        BeanDesc beanDesc = cachedBeanParser.get(voClass);
        Object domainObj = result.get(SimpleEntityPathResolver.INSTANCE
                .createPath(beanDesc.getEntityClass()));
        if (null != domainObj) {
            T vo = convertDomainToVO(domainObj, voClass);
            return vo;
        }
        return null;
    }

}
